﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.IO;
using System.Diagnostics;

using MediaInfoLib;

// INPUT:
// py file from avidemux
// adm.loadVideo("D:/download/mov/aaa.avi")
// adm.addSegment(0, 38605233, 8341667)
// adm.addSegment(0, 105338566, 8341667)        ==> 105.338566, 8.341667
// 

// OUTPUT:
// segment file used by asfbin
// <segments_list> format: - each line contains one segment description:
//  <start_time><separators><duration> e.g.: 14:45, 3:00
//  <time> in general is given in seconds, but it accepts
//  following formats as well:
//    1:59:45.35 = 1 h, 59 min, 45s, 35 hundredths, 3:30 = 3 min, 30 sec.,
//    1023.101 = 1023 sec. and 101 thousandths, etc.

namespace wmvSegments
{
    public class wmvCut
    {
        public class VideoFile
        {
            public string m_videoName;
            public string m_realWmvName;
            public double m_start;
            public double m_duration;

            public VideoFile(string videoName, VideoFile previous, MediaInfo mi)
            {
                m_videoName = videoName;
                m_start = previous == null ? 0 : previous.m_start + previous.m_duration;
                m_duration = GetVideoDuration(videoName,mi);
                m_realWmvName = videoName;
            }
        };

        public delegate void OutputHandler(object sender, DataReceivedEventArgs e);

        private const string LOAD_VIDEO = "adm.loadVideo";
        private const string APPEND_VIDEO = "adm.appendVideo";
        private const string SEGMENT = "adm.addSegment";
        private const string ASFBIN = @"d:\app\asfbin\asfbin.exe";
        private const string outputSegmentsFile = "$$tmp_segment$$.txt";
        
        private OutputHandler m_log;
        Process m_process;

        private MediaInfo m_mediaInfo;
        
        public string m_inputSegmentsFile { get; set; }
        public string m_outputPath { get; set; }
        public string m_outputWmv { get; set; }
        public List<VideoFile> m_videoList;
        public string m_segments;

        public static string getExtension(string FullPathName)
        {
            int index = FullPathName.LastIndexOf('.');
            if (index > 0)
                return FullPathName.Substring(index + 1);
            else
                return "";
        }

        public static double GetVideoDuration(String file, MediaInfo mi)
        {
            mi.Open(file);
            string durationString = mi.Get(StreamKind.Video, 0, "Duration").ToLower();
            mi.Close();

            double duration = int.Parse(durationString) / 1000.0;
            
            return duration;
        }    

        public static string getFileName(string FullPathName)
        {
            int idx = FullPathName.LastIndexOf("\\");
            if (idx > 0)
            {
                return FullPathName.Substring(idx + 1);
            }
            return FullPathName;
        }

        public static string getFileNameNoExt(string FullPathName)
        {
            string name = getFileName(FullPathName);
            int ext = name.LastIndexOf(".");
            if (ext > 0)
            {
                name = name.Substring(0, ext);
            }
            return name;
        }

        public static string findFile(string fileName)
        {
            string startPath = @"d:\download\";
            string[] directories = Directory.GetFiles(startPath, fileName, SearchOption.AllDirectories);
            if (directories.Length > 0)
            {
                return directories[0];
            }
            return null;
        }

        void Log(string msg)
        {
            //DataReceivedEventArgs e;
            //e.Data = msg;
            //m_log(this, e);
        }

        public wmvCut(string segmentFile, string outputPath, OutputHandler log)
        {
            m_videoList = new List<VideoFile>();
            m_inputSegmentsFile = segmentFile;
            m_outputPath = outputPath;
            m_log = log;
            m_mediaInfo = new MediaInfo();

            if (m_outputPath.Substring(m_outputPath.Length - 1, 1) != "\\")
            {
                m_outputPath = m_outputPath + "\\";
            }
            parseSegments();            
        }

        private void parseSegments()
        {
            string patternVideoName = "adm.loadVideo\\(\"(.*)\"\\)";
            string patternAppendVideoName = "adm.appendVideo\\(\"(.*)\"\\)";
            string patternSegment = "adm.addSegment\\((.*), (.*), (.*)\\)";
            Regex videoNameRegex = new Regex(patternVideoName, RegexOptions.IgnoreCase);
            Regex appendVideoNameRegex = new Regex(patternAppendVideoName, RegexOptions.IgnoreCase);
            Regex segmentRegex = new Regex(patternSegment, RegexOptions.IgnoreCase);
            
            m_outputWmv = null;
            m_segments = "";
            m_videoList.Clear();            

            StreamReader sr = new StreamReader(m_inputSegmentsFile, System.Text.Encoding.UTF8);

            // read project file
            while (sr.Peek() >= 0)
            {
                string line = sr.ReadLine();
                // find video name
                if (line.IndexOf(LOAD_VIDEO) != -1)
                {
                    Match m = videoNameRegex.Match(line);
                    if (m.Success && m.Groups.Count == 2)
                    {
                        string videoName = m.Groups[1].Captures[0].ToString().Replace('/', '\\');
                        VideoFile vf = new VideoFile(videoName, null, m_mediaInfo);
                        m_videoList.Add(vf);

                    }
                }
                else if (line.IndexOf(APPEND_VIDEO) != -1 && m_videoList.Count > 0)
                {
                    Match m = appendVideoNameRegex.Match(line);
                    if (m.Success && m.Groups.Count == 2)
                    {
                        string videoName = m.Groups[1].Captures[0].ToString().Replace('/', '\\');
                        VideoFile vf = new VideoFile(videoName, m_videoList.Last(), m_mediaInfo);
                        m_videoList.Add(vf);
                    }
                }
                else
                    // find segments
                    if (line.IndexOf(SEGMENT) != -1)
                    {
                        Match m = segmentRegex.Match(line);
                        if (m.Success && m.Groups.Count == 4)
                        {
                            Int32 videoIndex = Int32.Parse(m.Groups[1].Captures[0].ToString().Replace(" ", ""));
                            if (videoIndex >= m_videoList.Count)
                            {
                                continue;
                            }
                            string startTime = m.Groups[2].Captures[0].ToString().Replace(" ", "");
                            string duration = m.Groups[3].Captures[0].ToString().Replace(" ", "");

                            if (startTime.Length > 6)
                            {
                                startTime = startTime.Insert(startTime.Length - 6, ".");
                            }
                            else
                            {
                                if (startTime != "0")
                                {
                                    System.Console.Write("Error Segment start time: {0}", line);
                                }
                                startTime = "0";
                            }

                            duration = duration.Insert(duration.Length - 6, ".");

                            double timeOffset = m_videoList[videoIndex].m_start;
                            double fStartTime = double.Parse(startTime) + timeOffset;


                            m_segments += fStartTime.ToString() + ", " + duration + "\r\n";
                        }
                    }

            }
            sr.Close();
        }

        public void cut()
        {            
            bool bProjLoadSuccess = false;

            //1. write to segment file
            StreamWriter sw = new StreamWriter(outputSegmentsFile, false, Encoding.UTF8);
            sw.Write(m_segments);
            sw.Close();
            bProjLoadSuccess = true;

            //2. if source file is avi, means it's temp file, locate the real source wmv  
            string arguments = "";
            foreach(VideoFile vf in m_videoList)
            {
                if (getExtension(vf.m_videoName).ToLower() == "avi")
                {
                    string wmvNameToSearch = getFileNameNoExt(vf.m_videoName) + ".wmv";
                    wmvNameToSearch = findFile(wmvNameToSearch);
                    if (wmvNameToSearch != null)
                    {
                        vf.m_realWmvName = wmvNameToSearch;                        
                    }
                }
                arguments += " -i \"" + vf.m_realWmvName + "\"";
            }


            //3. set output wmv name
            m_outputWmv = m_outputPath + getFileName(m_videoList[0].m_realWmvName);

            //4. if output wmv file exists, add postfix
            bool targetExist = true;
            int postfixIdx = 0;
            string finalWmv = m_outputWmv;
            do
            {
                FileInfo fi = new FileInfo(finalWmv);
                if (fi.Exists)
                {
                    string postfix = '[' + postfixIdx.ToString() + ']';
                    finalWmv = m_outputWmv.Insert(m_outputWmv.IndexOf("."), postfix);
                    postfixIdx++;
                }
                else
                {
                    targetExist = false;
                }
            } while (targetExist);

            //5. run asfbin
            if (bProjLoadSuccess)
            {
                arguments += " -o \"" + finalWmv + "\" -s " + outputSegmentsFile;

                Log("---------------------------------------------------------\r\n\r\n");
                Log("Execute command:\r\n" + ASFBIN + " " + arguments + "\r\n\r\n\r\n");
                Log("---------------------------------------------------------\r\n");

                Process p = new Process();
                p.StartInfo.FileName = ASFBIN;
                p.StartInfo.Arguments = arguments;
                p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
                p.StartInfo.CreateNoWindow = true;
                p.StartInfo.RedirectStandardOutput = true;
                p.StartInfo.UseShellExecute = false;
                p.OutputDataReceived += (s, e) =>
                {
                    m_log(s, e);
                };

                //execute the command
                p.Start();
                p.BeginOutputReadLine();
                //p.WaitForExit();
                //p.Close();
                m_process = p;
            }

            // delete tmp file
            //             FileInfo sfi = new FileInfo(outputSegmentsFile);
            //             if (sfi.Exists)
            //             {
            //                 sfi.Delete();
            //             }
        }

        public void stop()
        {
            if (m_process != null)
            {
                m_process.Kill();
                m_process = null;
            }
        }

        public bool finish()
        {
            if (m_process != null)
            {
                return m_process.HasExited;
            }
            return true;
        }
    }
}
